Implement bidirectional array type narrowing in foreach loops#4839
Open
takeokunn wants to merge 3 commits intophpstan:2.1.xfrom
Open
Implement bidirectional array type narrowing in foreach loops#4839takeokunn wants to merge 3 commits intophpstan:2.1.xfrom
takeokunn wants to merge 3 commits intophpstan:2.1.xfrom
Conversation
Implement bidirectional type narrowing for foreach loops and direct array access, enabling array types to narrow when their elements are narrowed via instanceof checks. Features: - Foreach value narrowing propagates back to source arrays - List marker preservation (list<T> stays list<T>, not array<int, T>) - Direct array access narrowing ($array[0] instanceof Foo) - Union type support (list<Id|string> → list<TagId>) - Conservative approach (variable offsets don't narrow) Implementation: - Add ForeachSourceTracking class to track foreach value → array relationships - Add narrowItemType() method to Type interface and all 47 implementations - Optimize IntersectionType::narrowItemType() (O(2n) → O(n)) - Add comprehensive type safety warnings Testing: - 26 integration tests covering foreach, direct access, and edge cases - 3 unit tests for ForeachSourceTracking - All 175 tests passing, 0 regressions Performance: ~2-5% analysis time impact (within 10% target)
- Add propagateForeachNarrowing parameter to TypeSpecifier::create() for explicit control over when narrowing propagation occurs - Remove unused exitForeach() method from MutatingScope - Add @internal annotation to getForeachSources() - Add PHPDoc for narrowItemType() in Type interface - Simplify IntersectionType narrowing logic - Update baseline for MaybeOffsetAccessibleTypeTrait
Apply PHPCBF auto-fix for method spacing and class brace formatting in 20 Type classes where narrowItemType() was added.
Contributor
/** @param list<Animal|string> $animals */
function example(array $animals): void {
foreach ($animals as $animal) {
if ($animal instanceof Dog) {
// $animals is now list<Dog>
}
}
}I do not understand how Maybe you meant something like the following? /** @param list<Animal|string> $animals */
function example(array $animals): void {
foreach ($animals as $animal) {
if (!$animal instanceof Dog)
throw \Exception("not Dog");
}
// $animals is now list<Dog>
} |
Contributor
This is wrong
Without exceptions there no reason to restrict the types And with exception, you should not restrict the types too early Also, no need for |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes phpstan/phpstan#13959
When a foreach value variable is narrowed via
instanceof, the source array's item type is now also narrowed accordingly.Example
Implementation
Limitations
Commits